home *** CD-ROM | disk | FTP | other *** search
/ The Atari Compendium / The Atari Compendium (Toad Computers) (1994).iso / files / umich / telecomm / stadel / stadel2.zoo / document / hack.doc < prev    next >
Encoding:
Text File  |  1989-08-11  |  19.1 KB  |  406 lines

  1.                      The internals of Citadel -- not
  2.                           for the weak-hearted
  3.  
  4. 1) OVERVIEW
  5.  
  6.     The fundamental structure of the system is very simple.  
  7.     CTDLMSG.SYS is a circular file.  New messages are simply written 
  8.     around the buffer in an endless circle, overwriting old messages
  9.     in their way.  There is no other way of deleting messages, and
  10.     text is never shuffled on disk.  Messages are numbered
  11.     consecutively and start with an FF (hex) byte.  Except for this FF
  12.     start-of-message byte, all bytes in the message file have the high
  13.     bit set to 0.  This means that in principle it is trivial to scan
  14.     through the message file and locate message N if it exists, or
  15.     return error.  (Complexities, as usual, crop up when we try for
  16.     efficiency...) 
  17.  
  18.     Each room contains a list of message numbers.  Each time we enter
  19.     a new message in a room, we slide all the old message-numbers 
  20.     down a slot, and probably the oldest one falls off the bottom.  
  21.     Reading a room is just a matter looking up the messages one by one
  22.     and printing them out.  If the message has been overwritten
  23.     already, we just ignore it.  
  24.  
  25.     Implementing the "new message" function is also trivial in 
  26.     principle:  we just keep track, for each caller in the userlog, of
  27.     the highest-numbered message which existed on the >last< call.  
  28.     (Remember, message numbers are simply assigned sequentially each 
  29.     time a message is created.  This sequence is global to the entire 
  30.     system, not local within a room.) If we ignore all message-numbers
  31.     in the room less than this, only new messages will be printed.
  32.     Voila!  Code up the system described thus far, and you'll have a
  33.     good approximation to Version 1.  Better stop and reread
  34.     everything to here, so you can pick out the fundamental mechanisms
  35.     among all of Version 2's bells and whistles.  
  36.  
  37.  
  38. 2) MESSAGE FORMAT ON DISK (CTDLMSG.SYS)
  39.  
  40.     A message consists of a sequence of character strings.  Each 
  41.     string begins with a type byte indicating the meaning of the
  42.     string and is ended with a null.  All strings are printable ASCII:
  43.     in particular, all numbers are in ASCII rather than binary.  This
  44.     is for simplicity, both in implementing the system and in
  45.     implementing other code to work with the system.  For instance, a
  46.     database driven off Citadel archives can do wildcard matching
  47.     without worrying about unpacking binary data such as dates first.
  48.     To provide later downward compatability, all software should be
  49.     written to IGNORE fields not currently defined.  
  50.  
  51.                   The type bytes currently defined are:
  52.  
  53.     BYTE Mnemonic    Comments
  54.  
  55.     0xFF        Start-of-message indicator.  Followed by local
  56.             message ID# as ASCII string, null-terminated as
  57.             always.  This byte is the >only< byte which has
  58.             the high bit set in a Citadel message.buf file.
  59.             This field must be present in every message.
  60.     A    Author        Name of originator of message.
  61.     C    Time        Time message was created.
  62.     D    Date        Date message was created.
  63.     I    Info        Extra information about the originating system
  64.             (not found in non-net messages) that is displayed
  65.             in the message header.
  66.     J    Subject        Subject field.
  67.     M    Message     Text of message.  Is last field in a message, by
  68.             definition.  Following data will be ignored.
  69.             This field must be present in every message.
  70.     N    Name        Human name for node originated on.  Used on
  71.             title line of foreign messages.  Ex:
  72.             ODD-DATA
  73.             will produce a title message something like
  74.             82Nov23 from Cynbe ru Taren @ODD-DATA
  75.     O    Origin        ID of node message originated on: Country code plus
  76.             phone number of system.  (Not stored for locally
  77.             originated messages.)  Ex:
  78.             US 206 633 3282
  79.     R    Room        Room of origin.  Topic.
  80.     S    Source ID#    Message ID # on system message was created on.
  81.             Two 16-bit integers (high and low halves of
  82.             full 32-bit ID#) separated by a blank.    Ex:
  83.             0 13654
  84.     T    To        Addressee.  Used for private mail messages.
  85.     Q   ?        Citadel-86 internal.
  86.     P   ?        Citadel-86 internal.
  87.     #    Message-ID    Usenet-style message-id.
  88.     Z    Route        Routing code for roomsharing.  See NETHACK.DOC
  89.             for details on it.
  90.  
  91. 2.1) EXAMPLE
  92.  
  93.     Let <FF> be a 0xFF byte, and <0> be a null (0x00) byte.  Then a 
  94.     message which prints as 
  95.  
  96.     LOGLAN> read new
  97.  
  98.     82Nov04 From James Cooke Brown
  99.     Loi uiue i Ti logla
  100.  
  101.     might be stored as 
  102.  
  103. <FF>0 3583<0>D82Nov04<0>AJames Cooke Brown<0>RLOGLAN<0>MLoi uiue i Ti logla<0>
  104. |--Local ID--|---Date---|-----Author---------|--Room---|-------Message--------|
  105.  
  106.     The date, room and author fields could be in any order.  Not all 
  107.     fields are printed by default.  The local ID# and Room field are 
  108.     suppressed here.  An isolated system will not normally have use
  109.     for fields beyond those used in this example.  
  110.  
  111.     Lines are marked with C Newline (ASCII LF) characters, within the
  112.     message text proper.  Returns (ASCII CR) are also used for _hard_ 
  113.     carriage returns;  when encountered, the formatter will
  114.     _immediately_ do a newline.  
  115.  
  116.  
  117. 3) ROOM RECORDS (CTDLROOM.SYS)
  118.  
  119.     The rooms are indices into CTDLMSG.SYS, the message file.  As 
  120.     noted in the overview, each is essentially an array of pointers
  121.     into the message file.  The pointers consist of a 32-bit message
  122.     ID number together with a 16-bit psuedo-sector offset within
  123.     CTDLMSG.SYS telling us where the message begins.  
  124.  
  125.     Since messages are number sequentially and written circularly, 
  126.     the set of messages existing in CTDLMSG.SYS will always form a 
  127.     continuous sequence at any given time.  Thus, by remembering the
  128.     ID numbers of the oldest and newest messages in the message file,
  129.     we can check to see if a message exists >before< going to disk,
  130.     saving ourselves (and the disk drive) the pain of futile seeks in
  131.     search of the nonexistent.  This information is recorded in oldest
  132.     and newest, 32 bit numbers.  You'll be seeing more of these...  
  133.  
  134.     The newest is simply incremented each time we enter a new message
  135.     in the message files.  Oldest is incremented each time we 
  136.     overwrite an FF (start-of-message) byte in the course of writing a
  137.     new message into the files.  This corresponds to dead reckoning --
  138.     current code never checks to see that the message number of the 
  139.     message we are overwriting is what we think it is.  In a garbaged 
  140.     file with extra FF bytes around, this could cause oldest to count 
  141.     too rapidly, eventually perhaps overtaking newest, at which time
  142.     the system will look completely empty.  If you suspect something
  143.     like this is going on, just use configur.exe to rebuild accurate
  144.     numbers.  
  145.  
  146.     That should be enough background to tackle a full-scale room.  From
  147.     ctdl.h :  
  148.  
  149.     struct aRoom {        /* The appearance of a room:        */
  150.     unsigned rbgen;        /* generation # of room         */
  151.     struct rflags rbflags;    /* same bits as flags above        */
  152.     label    rbname;        /* name of room             */
  153.     ulong    rblastNet;
  154.     ulong    rblastLocal;
  155.     ulong    rblastMessage;
  156.     char    rbfill[8];
  157.     pathbuf    rbdirname;    /* user directory for this room's files */
  158.     struct aRmsg {
  159.         ulong rbmsgNo;    /* every message gets unique#        */
  160.         int   rbmsgLoc;    /* sector message starts in        */
  161.     } msg[MSGSPERRM];
  162.     } ;
  163.  
  164.     [Note that all components start with "rb" for roomBuf, to make
  165.     sure we don't accidentally use an offset in the wrong structure.
  166.     Be very careful also to get a meaningful sequence of components --
  167.     C lets you mix and match structure components without complaint.] 
  168.  
  169.     Rbgen handles the problem of rooms which have died and been 
  170.     reborn under another name.  This will be clearer when we get to
  171.     the log file.  For now, just note that each room has a generation
  172.     number which is bumped by one each time it is recycled.  
  173.  
  174.     Rbflags is just a bag of flags recording the status of the room.  
  175.     The defined flags are:  
  176.  
  177.     INUSE        Is this room in use?
  178.     PUBLIC        Is this room public?
  179.     ISDIR        Is this room directory?
  180.     PERMROOM    Is this room permanent?
  181.     UPLOAD        Can files be written to this room?
  182.     DOWNLOAD    Can files be read from this room?
  183.     SHARED        Is this a shared room?
  184.     ARCHIVE        Is this room archived somewhere?
  185.     ANON        is this an anonymous room?
  186.     INVITE        is this an invitation-only room?
  187.     NETDOWNLOAD    Can files be grabbed by network callers?
  188.     AUTONET        Net all the messages entered in this room?
  189.  
  190.     Rbname is just an ASCII string (null-terminated, like all strings)
  191.     giving the name of the room.  
  192.  
  193.     Rbdirname is meaningful only in ISDIR rooms, in which case it 
  194.     gives the full pathname of the directory to window.  
  195.  
  196.     Finally, msg is the array of pointers into the message file.  
  197.     RbmsgNo is the ID number of the message, and RbmsgLoc is the
  198.     sector it begins in.  (For NIL, we stick the value -1 in
  199.     RbmsgLoc.) 
  200.  
  201.  
  202.     RoomTab is just a little index into CTDLROOM.SYS, to keep us from
  203.     constantly pounding around on the disk looking for things.  It 
  204.     basically records the name and status of each room.  It is 100% 
  205.     redundant with the file itself...  as all our indices are.  (As
  206.     all indices should be!) Note that RoomTab is a significant
  207.     consumer of RAM all by itself.  It is RAM well spent, but if you
  208.     have to shave Citadel a few K to make it fit your system, cutting
  209.     the number of rooms a bit is one try.  
  210.  
  211.     The only field new to us in roomTab is rtlastMessage, recording
  212.     the most recent message in the room.  When we are searching for
  213.     rooms with messages a given caller hasn't seen, we can check this
  214.     number in RAM and avoid unprofitable disk accesses.  
  215.  
  216.  
  217. 4) LOG RECORDS (CTDLLOG.SYS)
  218.  
  219.     This is the fun one.  Get some fresh air and plug in your 
  220.     thinking cap first.  (Time, space and complexity are the eternal 
  221.     software rivals.  We've got however many log entries x about 500 
  222.     messages spread over up to 128 rooms to worry about, and with 
  223.     floppies disk access time is important...  so perforce, we opt for
  224.     lots of complexity to keep time and space in bounds.) 
  225.  
  226.     To understand what is happening in the log code takes a little 
  227.     persistence.  You also have to disentangle the different
  228.     activities going on and tackle them one by one.  
  229.  
  230.     o We want to remember some random things such as terminal screen 
  231.     size, and automatically set them up for each caller at login.  
  232.  
  233.     o We want to be able to locate all new messages, and only new 
  234.     messages, efficiently.  Messages should stay new even if it takes
  235.     a caller a couple of calls to get around to them.  
  236.  
  237.     o We want to remember which private rooms a given caller knows 
  238.     about, and treat them as normal rooms.  This means mostly 
  239.     automatically seeking out those with new messages.  (Obviously, 
  240.     we >don't< want to do this for unknown private rooms!) This has to
  241.     be secure against the periodic recycling of rooms between calls.  
  242.  
  243.     o We want to support private mail to a caller.  
  244.  
  245.     o We want to provide some protection of this information (via 
  246.     passwords at login) and some assurance that messages are from who
  247.     they purport to be from (within the system -- one shouldn't be
  248.     able to forge messages from established users).  
  249.  
  250.     Lifting another page from ctdl.h gives us:  
  251.  
  252.     struct logBuffer {        /* The appearance of a user:        */
  253.     uchar lbnulls;        /* #nulls                */
  254.     struct lflags lbflags;    /* UCMASK, LFMASK, EXPERT, AIDE, INUSE    */
  255.     uchar lbwidth;        /* terminal width            */
  256.     int   credit;        /* Credit for long distance calls    */
  257.     label lbname;        /* caller's name            */
  258.     label lbpw;         /* caller's password            */
  259.     long  lblimit;        /* # bytes the user can download today    */
  260.     long  lblast;        /* last day the user logged in        */
  261.     uchar lbgen[MAXROOMS];    /* 5 bits gen, 3 bits lastvisit        */
  262.     ulong lbvisit[MAXVISIT];/* newestLo for this and 3 prev. visits */
  263.     unsigned lbslot[MSGSPERRM];    /* for private mail        */
  264.     ulong lbId[MSGSPERRM];        /* for private mail        */
  265.     } ;
  266.  
  267.     Looks simple enough, doesn't it?  One topic at a time:  
  268.  
  269. 4.1) RANDOM CONFIGURATION PARAMETERS
  270.  
  271.     These are in the first three fields in the record.  Lbnulls gives
  272.     the number of nulls to print after a newline, for folks who like
  273.     simultaneous hardcopy.  Or any remaining ASR33 aficionados 
  274.     calling up...  Lbwidth is the caller's screen width.  We format
  275.     all messages to this width, as best we can.  Lbflags is another
  276.     bit-bag, recording uppercase-only folks, people who need a
  277.     linefeed after a carraige-return, people who want to suppress the
  278.     little automatic hints all through the system, and people who like
  279.     to see the time a message was created.  
  280.  
  281.  
  282. 4.2) FINDING NEW MESSAGES
  283.  
  284.     This is the most important.  Thus, it winds up being the most 
  285.     elaborate.  Conceptually, what we would like to do is mark each 
  286.     message with a bit after our caller has read it, so we can avoid 
  287.     printing it out again next call.  Unfortunately this would suck 
  288.     disk space like mad and you'd have to read a lot of unprinted
  289.     message off disk every time a user passes through.  
  290.  
  291.     The approximation comes in doing things at the granularity of 
  292.     rooms rather than messages.  Messages in a given room are "new" 
  293.     until we visit it, and "old" after we leave the room...  whether
  294.     we read any of them or not.  This can actually be defended:  anyone
  295.     who passes through a room without reading the contents probably
  296.     just isn't interested in the topic, and would just as soon not be
  297.     dragged back every visit and forced to read them.  Given that
  298.     messages are numbered sequentially, we can simply record the most
  299.     recent message ID# of each room as of the last time we visited it.
  300.     With 128 rooms, this would give us (for each user) an array of 128
  301.     longs, or 512 bytes.  
  302.  
  303.     This is still too much -- I'd like the complete log record for a 
  304.     user to be as tiny as possible, and we have other stuff to do yet.
  305.  
  306.     So, we complicate a little more.  We record in lbvisit[MAXVISIT] 
  307.     the most recent message ID# in the system on each of the last six 
  308.     calls or so.  Now, for each room, we can just indicate which call 
  309.     we last visited the room on.  This takes 3 bits per room, which we
  310.     stash in the low three bits of lbgen[MAXROOMS].  Now we're down to
  311.     128 rooms x 3 bits (plus a few bytes in lbvisit[], of course),
  312.     which is quite reasonable.  
  313.  
  314.     Putting it all together, we can now compute whether a given room 
  315.     has new messages for our current caller without going to disk at
  316.     all:  
  317.  
  318.      > We get the lbgen[] entry for the room in question
  319.      > We mask off the lower 3 bits
  320.      > We use the result as an index into lbvisit[], getting the ID number
  321.        of the most recent message in the system as of the last time we
  322.        visited the room.
  323.      > We compare this with roomTab[].rtlastMessage, which tells us
  324.        what the most recent message in the room is currently.
  325.  
  326.  
  327. 4.3) REMEMBERING WHICH PRIVATE ROOMS TO VISIT
  328.  
  329.     This looks trivial at first glance -- just record one bit per 
  330.     room per caller in the log records.  The problem is that rooms get
  331.     recycled periodically, and we'd rather not run through all the log
  332.     entries each time we do it.  So we adopt a kludge which should
  333.     work 99% of the time.  
  334.  
  335.     As previously noted, each room has a generation number, which is 
  336.     bumped by one each time it is recycled.  As not noted, this 
  337.     generation number runs from 0 -> 31 (and then wraps around and 
  338.     starts over).  Thus, these numbers take 5 bits to represent.  By a
  339.     miraculous coincidence, we have exactly 5 bits left in the lbgen[]
  340.     entries in the log records.  [Anyone familiar with "capability" 
  341.     pointers will be encountering deja vu here...] 
  342.  
  343.     When someone visits a room, we set the generation number in
  344.     lbgen[] equal to that of the room.  This flags the room as being
  345.     available.  If the room gets recycled, on our next visit the two
  346.     generation numbers will no longer match, and the room will no
  347.     longer be available -- just the result we're looking for.
  348.     (Naturally, if a room is marked as PUBLIC, all this stuff is
  349.     irrelevant.) 
  350.  
  351.     This leaves only the problem of an accidental matchup between the
  352.     two numbers giving someone access to a Forbidden Room.  We can't 
  353.     eliminate this danger completely, but it can be reduced to 
  354.     insignificance for most purposes.  (Just don't bet megabucks on
  355.     the security of this system!) Each time someone logs in, we set
  356.     all "wrong" generation numbers to be one less than the actual
  357.     generation of the room.  This means that the room must be recycled
  358.     thirty-one times before an accidental matchup can be achieved.  (We
  359.     do this for all rooms, INUSE or dead, public or private, since any
  360.     of them may be reincarnated as a Forbidden Room.) 
  361.  
  362.     Thus, for someone to accidentally be lead to a Forbidden Room, 
  363.     they must establish an account on the system, then not call until 
  364.     some room has been recycle thirty-one to thirty-two times, which 
  365.     room must be reincarnated as a Forbidden Room, which someone must 
  366.     now call back (having not scrolled off the userlog in the mean
  367.     time) and read new messages.  The last clause is about the only
  368.     probable one in the sequence.  The danger of this is much less
  369.     than the danger that someone will simply guess the name of the
  370.     room outright...  
  371.  
  372.  
  373. 4.4) SUPPORTING PRIVATE MAIL
  374.  
  375.     Can one have an elegant kludge?  This must come pretty close.  
  376.  
  377.     Private mail is sent and recieved in the Mail> room, which 
  378.     otherwise behaves pretty much as any other room.  To make this
  379.     work, we store the actual message pointers in lbslot[] and lbId[]
  380.     in the caller's log record, and then copy them into the Mail> room
  381.     array whenever we enter the room.  This requires a little fiddling
  382.     to get things just right.  We have to update
  383.     roomTab[MAILROOM].rtlastMessage at login to reflect the presence
  384.     or absence of new messages, for example.  And MakeMessage() has to
  385.     be kludged to ask for the name of the recipient of the message
  386.     whenever a message is entered in Mail>.  But basically it works
  387.     pretty well, keeping the code and user interface simple and
  388.     regular.  
  389.  
  390.  
  391. 4.5) PASSWORDS AND NAME VALIDATION
  392.  
  393.     LogTab[] indexes CTDLLOG.SYS, giving us a quick way of finding 
  394.     people.  It is basically a chronologically sorted hash table.  We 
  395.     keep a two-byte hash of the name and password of each caller in
  396.     RAM.  When someone tries to log in, we just whip through the table
  397.     in order looking for matches on the password hash and loading the 
  398.     matching logfile entry in.  Bogus hits are eliminated by the
  399.     simple expedient of refusing to acknowledge a new user who's name
  400.     or password hashes on top of an existing user.  Computer
  401.     chauvinism at it's best...  
  402.  
  403.     This makes it difficult to forge messages from an existing user.  
  404.     (Fine point:  nonprinting characters are converted to printing 
  405.     characters, and leading, trailing, and double blanks are deleted.)
  406.